package com.angelstone;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Hashtable;

public class L10nResources {
	
    // Constants -----------------------------------------------------
    
	private static final String PROPERTIES_PCKG = "";
	private static final String PROPERTIES_PRFX = "messages";
    private static final String DEFAULT_LOCALE  = "";
	
	private static final String UNSET_LOCALE_MESSAGE 
			= "Set locale before retrieving resources.";
	private static final String INVALID_STRING_FORMAT 
			= "The String format is invalid.";
	private static final String LOCALE_NOT_FOUND 
			= "No resource found for locale";
	private static final String INVALID_RESOURCE_FORMAT 
			= "The resource file format is invalid.";
	private static final String ERROR_LOADING_RESOURCES 
			= "Error loading resources.";
	private static final String UNABLE_TO_FIND_BINARY_RESOURCE 
			= "Unable to find binary resource.";
	
	// Attributes ----------------------------------------------------
	
	private Hashtable values;
	private String 	  locale;
	
    // Static --------------------------------------------------------
	
	/**
	 * Gets an instance of L10nResources to access resources with
	 * the specified locale.
	 * 
	 * @param locale name of locale.
	 * @return L10nResources instance. 
	 */
	public static L10nResources getL10nResources(String locale) {
		return new L10nResources(locale);
	}
	
    // Constructors --------------------------------------------------
	
	/**
	 * Creates an instance of L10nResources to access resources 
	 * with the specified locale.
	 * 
	 * @param locale name of locale.
	 * @return L10nResources instance. 
	 */
	private L10nResources(String locale) {
		this.values = new Hashtable();
		this.setLocale(locale);
	}
	
    // Public --------------------------------------------------------
	
	
	/**
	 * Sets the locale of the L10nResources instance.
	 * 
	 * @param locale name of the locale.
	 */
	public void setLocale(String locale) {
		if (locale == null || locale.length() == 0x00) {
			this.locale = System.getProperty("microedition.locale");
			if (this.locale == null) {
				this.locale = L10nResources.DEFAULT_LOCALE;
			}
		} else {
			this.locale = locale;
		}
		
		try {			
			this.loadResources(this.locale);
		} catch (Exception exc) {
			exc.printStackTrace();
		}
	}
	
	
	/**
	 * Gets the value for the specified key. For every parameter
	 * on the params argument there must be an entry in the key
	 * value in the format {x} where x is the index of the value
	 * to be replaced on the formated string.
	 * 
	 * @param key resource key.
	 * @param params parameters to be formated.
	 * @return the formated value.
	 */
	public String getString(String key, String[] params) {
		String result = null;
		if (this.values == null) {
			throw new IllegalStateException(UNSET_LOCALE_MESSAGE);
		}
		result = (String) this.values.get(key);
		if (result != null) {
			StringBuffer buffer = new StringBuffer(result);
			for (int i = 0; i < params.length; i++) {
				int index = result.indexOf("{" + i + "}");
				if (index == -1) {
					throw new IllegalArgumentException(INVALID_STRING_FORMAT);
				}
				
				buffer.delete(index, index + String.valueOf(i).length() + 2);
				buffer.insert(index, params[i]);
				result = buffer.toString();
				buffer.delete(0x00, buffer.length());
				buffer.append(result);
			}
		}
		return result;
	}
	
	
	/**
	 * Gets the value for the specified key.
	 * 
	 * @param key resource key.
	 * @return String values associated to the
	 * key. 
	 */
	public String getString(String key) {
		if (this.values == null) {
			throw new IllegalStateException(UNSET_LOCALE_MESSAGE);
		}
		return (String) this.values.get(key);
	}
	
	
	/**
	 * Gets the binary data associated to the path
	 * set as value of the specified key.
	 * 
	 * @param key resource key.
	 * @return the bytes of the binary data or null
	 * if key has no associated value.
	 * @throws IOException - If any error occurs.
	 */
	public byte[] getData(String key) throws IOException {
		byte[] result = null;
		String value  = getString(key);
		if (value != null) {
			InputStream  stream = this.getClass().getResourceAsStream(value);
			if (stream == null) {
				throw new IOException(UNABLE_TO_FIND_BINARY_RESOURCE);
			}
			
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			try {					
				byte[] chunk = new byte[100];
				int	   read	 = -1;
				do {
					read = stream.read(chunk);
					if (read > 0x00) {
						buffer.write(chunk, 0x00, read);
					}
				} while (read != -1);
			} finally {
				stream.close();
			}
			result = buffer.toByteArray();
		}
		return result;
	}
	
    // Package protected ---------------------------------------------
    
    // Protected -----------------------------------------------------
    
    // Private -------------------------------------------------------
	
	private void loadResources(String locale) throws IOException {
		
		Reader reader = null;
		this.values.clear();
		try {
			reader = getResourceReader(locale);
			if (reader != null) {				
				values = readResources(reader);
			}
		}  finally {
			if (reader != null) reader.close();
		}
	}

	private Reader getResourceReader(String locale) throws IOException {
		Reader reader = null;
		
		String resourePath = getResourcePath(locale);
		InputStream stream = this.getClass().getResourceAsStream(resourePath);
		if (stream == null) {
			throw new IOException(LOCALE_NOT_FOUND + " " + this.locale + ".");
		} else {
			reader = new InputStreamReader(stream);
		}
		
		return reader;
	}
	
	private Hashtable readResources(Reader reader) throws IOException {
		Hashtable	 values = new Hashtable();
		StringBuffer buffer = new StringBuffer();
		
		try {
			int letter = -1;
			while ((letter = reader.read()) != -1) {
				switch (letter) {
					case '\r':
					case '\n': 
					break;
					default:					
						buffer.append((char)letter);
					break;
				}
				
				if (letter == '\n') {
					String line   = buffer.toString();
					int separator = line.indexOf("=");	
					if (separator < 0) {
						throw new IOException(INVALID_RESOURCE_FORMAT);
					}
					String key 	 = line.substring(0x00, separator);
					String value = line.substring(++separator, line.length());
					values.put(key, value);
					buffer.delete(0x00, buffer.length());
				}
			}
		} catch (Exception e) {
			throw new IOException(ERROR_LOADING_RESOURCES);
		}		
		return values;
	}

	private String getResourcePath(String locale) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("/");
		buffer.append(PROPERTIES_PCKG);
		buffer.append("/");
		buffer.append(PROPERTIES_PRFX);
		if (locale.length() > 0) {			
			buffer.append("_");
		}
		buffer.append(locale);
		buffer.append(".properties");
		return buffer.toString();
	}
	
}
